Skip to content

FEC-1010: add positionFixed prop and resize-based reflow to prevent viewport clipping#3260

Merged
tylermorrisford merged 3 commits into
mainfrom
fec-1010-image-clipping-in-tooltip
Jun 15, 2026
Merged

FEC-1010: add positionFixed prop and resize-based reflow to prevent viewport clipping#3260
tylermorrisford merged 3 commits into
mainfrom
fec-1010-image-clipping-in-tooltip

Conversation

@tylermorrisford

Copy link
Copy Markdown
Contributor

Problem

When a TooltipWrapperComponent renders the tooltip body into a React portal
(e.g. ReactDOM.createPortal(..., document.body)), tooltips near the bottom of the
page render partially outside the visible viewport.

Two root causes were identified:

1. position: absolute coordinate drift
Popper.js defaults to position: absolute. When the scroll container is not
<body> (e.g. the MC app scrolls an inner div), the absolute coordinates Popper
computes diverge from the viewport, causing the portal tooltip to paint at the
wrong location.

2. Position computed before content has loaded
Popper.js calculates the tooltip's position when the popper node is first mounted.
If the body contains an <img> that has not yet loaded, the container reports 0
height at that moment. Once the image loads and the container grows, Popper.js never
recomputes — the tooltip extends below the visible area.


Fix

positionFixed prop

A new optional positionFixed?: boolean prop is threaded through to usePopper.
When true, Popper.js uses position: fixed for the popper element instead of
position: absolute. Fixed positioning is always viewport-relative and bypasses any
scrolled ancestor that would otherwise shift absolute coordinates.

Consumers should pair this with:

modifiers={{
  flip: { enabled: false },
  preventOverflow: { boundariesElement: 'viewport' },
}}

flip disabled prevents Popper from switching placement (e.g. "top""bottom")
near the page edge. preventOverflow with boundariesElement: 'viewport' aligns the
boundary with the fixed-position coordinate system so the tooltip is clamped correctly.

ResizeObserver reflow

A ResizeObserver is attached to the popper element whenever the tooltip is open.
If the element's dimensions change after the initial position was computed (the common
case when an <img> loads lazily inside the body), scheduleUpdate() is called so
Popper.js recomputes with the correct height and preventOverflow can clamp it within
the viewport. The observer is disconnected when the tooltip closes.


Files changed

  • packages/components/tooltip/src/tooltip.tsx

Backward compatibility

  • positionFixed is optional and defaults to false, preserving existing behavior
    for all current consumers.
  • No other public props or the component API are changed.

@tylermorrisford tylermorrisford requested a review from a team as a code owner June 15, 2026 17:12
@tylermorrisford tylermorrisford self-assigned this Jun 15, 2026
@changeset-bot

changeset-bot Bot commented Jun 15, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: f3ce9b4

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 97 packages
Name Type
@commercetools-uikit/tooltip Patch
@commercetools-frontend/ui-kit Patch
@commercetools-uikit/design-system Patch
@commercetools-uikit/calendar-time-utils Patch
@commercetools-uikit/calendar-utils Patch
@commercetools-uikit/hooks Patch
@commercetools-uikit/i18n Patch
@commercetools-uikit/localized-utils Patch
@commercetools-uikit/utils Patch
@commercetools-uikit/accessible-hidden Patch
@commercetools-uikit/avatar Patch
@commercetools-uikit/card Patch
@commercetools-uikit/collapsible-motion Patch
@commercetools-uikit/collapsible-panel Patch
@commercetools-uikit/collapsible Patch
@commercetools-uikit/constraints Patch
@commercetools-uikit/data-table-manager Patch
@commercetools-uikit/data-table Patch
@commercetools-uikit/field-errors Patch
@commercetools-uikit/field-label Patch
@commercetools-uikit/field-warnings Patch
@commercetools-uikit/filters Patch
@commercetools-uikit/grid Patch
@commercetools-uikit/icons Patch
@commercetools-uikit/label Patch
@commercetools-uikit/link Patch
@commercetools-uikit/loading-spinner Patch
@commercetools-uikit/messages Patch
@commercetools-uikit/notifications Patch
@commercetools-uikit/pagination Patch
@commercetools-uikit/primary-action-dropdown Patch
@commercetools-uikit/progress-bar Patch
@commercetools-uikit/quick-filters Patch
@commercetools-uikit/stamp Patch
@commercetools-uikit/tag Patch
@commercetools-uikit/text Patch
@commercetools-uikit/view-switcher Patch
@commercetools-uikit/accessible-button Patch
@commercetools-uikit/flat-button Patch
@commercetools-uikit/icon-button Patch
@commercetools-uikit/link-button Patch
@commercetools-uikit/primary-button Patch
@commercetools-uikit/secondary-button Patch
@commercetools-uikit/secondary-icon-button Patch
@commercetools-uikit/dropdown-menu Patch
@commercetools-uikit/async-creatable-select-field Patch
@commercetools-uikit/async-select-field Patch
@commercetools-uikit/creatable-select-field Patch
@commercetools-uikit/date-field Patch
@commercetools-uikit/date-range-field Patch
@commercetools-uikit/date-time-field Patch
@commercetools-uikit/localized-multiline-text-field Patch
@commercetools-uikit/localized-text-field Patch
@commercetools-uikit/money-field Patch
@commercetools-uikit/multiline-text-field Patch
@commercetools-uikit/number-field Patch
@commercetools-uikit/password-field Patch
@commercetools-uikit/radio-field Patch
@commercetools-uikit/search-select-field Patch
@commercetools-uikit/select-field Patch
@commercetools-uikit/text-field Patch
@commercetools-uikit/time-field Patch
@commercetools-uikit/async-creatable-select-input Patch
@commercetools-uikit/async-select-input Patch
@commercetools-uikit/checkbox-input Patch
@commercetools-uikit/creatable-select-input Patch
@commercetools-uikit/date-input Patch
@commercetools-uikit/date-range-input Patch
@commercetools-uikit/date-time-input Patch
@commercetools-uikit/input-utils Patch
@commercetools-uikit/localized-money-input Patch
@commercetools-uikit/localized-multiline-text-input Patch
@commercetools-uikit/localized-rich-text-input Patch
@commercetools-uikit/localized-text-input Patch
@commercetools-uikit/money-input Patch
@commercetools-uikit/multiline-text-input Patch
@commercetools-uikit/number-input Patch
@commercetools-uikit/password-input Patch
@commercetools-uikit/radio-input Patch
@commercetools-uikit/rich-text-input Patch
@commercetools-uikit/rich-text-utils Patch
@commercetools-uikit/search-select-input Patch
@commercetools-uikit/search-text-input Patch
@commercetools-uikit/select-input Patch
@commercetools-uikit/select-utils Patch
@commercetools-uikit/selectable-search-input Patch
@commercetools-uikit/text-input Patch
@commercetools-uikit/time-input Patch
@commercetools-uikit/toggle-input Patch
@commercetools-uikit/spacings-inline Patch
@commercetools-uikit/spacings-inset-squish Patch
@commercetools-uikit/spacings-inset Patch
@commercetools-uikit/spacings-stack Patch
@commercetools-uikit/buttons Patch
@commercetools-uikit/fields Patch
@commercetools-uikit/inputs Patch
@commercetools-uikit/spacings Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Jun 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ui-kit Ready Ready Preview, Comment Jun 15, 2026 5:20pm

Request Review

@valoriecarli valoriecarli left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for keeping at it with this adventure!!

@tylermorrisford tylermorrisford merged commit cd1a1b5 into main Jun 15, 2026
9 checks passed
@tylermorrisford tylermorrisford deleted the fec-1010-image-clipping-in-tooltip branch June 15, 2026 17:27
@ct-changesets ct-changesets Bot mentioned this pull request Jun 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants